'use strict';

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

(function () {
	function JhVirtualScroll(el, _ref) {
		var _this = this;

		var fetch = _ref.fetch,
		    render = _ref.render,
		    skeleton = _ref.skeleton;
		var extraCount = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 10;

		this.el = el; // 目标元素
		this.fetch = fetch; // 获取内容方法
		this.render = render; // 渲染方法
		this.skeleton = skeleton; // 创建墓碑方法
		this.firstAttachedIndex = 0; // 开始添加索引
		this.lastAttachedIndex = 0; // 最后添加索引
		this.anchorPoint = { index: 0, offset: 0, scrollTop: 0 }; // 锚点
		this.tombstoneWidth = 0; // 墓碑宽度
		this.tombstoneHeight = 0; // 墓碑高度
		this.vnodeList = []; // 节点列表
		this.loadedIndex = 0; // 已加载索引
		this.isbusy = false; // 是在在忙
		this.extraCount = extraCount; // 额外添加的个数
		this.el.style.overflowY = 'auto';
		this.el.style.contain = 'layout';
		this.el.style.boxSizing = 'border-box';
		this.el.style.willChange = 'transform';
		this.el.addEventListener('scroll', function (e) {
			_this.onScroll(e); // 监听滚动事件
		});
		window.addEventListener('resize', function () {
			_this.onResize(); // 监听尺寸变化事件
		});
		this.onResize(); // 初始化
	}

	JhVirtualScroll.prototype = {
		/**
   * 重载
   * @param {*} func 回调函数
   */
		onReload: function onReload(func) {
			var _this2 = this;

			this.firstAttachedIndex = 0;
			this.lastAttachedIndex = 0;
			Object.keys(this.anchorPoint).forEach(function (key) {
				_this2.anchorPoint[key] = 0;
			});
			this.vnodeList.splice(0, this.vnodeList.length);
			this.loadedIndex = 0;
			this.isbusy = false;
			while (this.el.childNodes.length > 0) {
				this.el.removeChild(this.el.childNodes[0]);
			}
			if (func instanceof Function) func();
			this.onResize();
		},


		/**
   * 尺寸变化时触发
   */
		onResize: function onResize() {
			this.initTombstoneSize();
			this.initVnodeSize();
			this.onScroll();
		},


		/**
   * 滚动时触发
   * @param {*} event
   */
		onScroll: function onScroll(event) {
			if (!this.checkScroll()) return false;
			var diff = this.getScrollDiff();
			if (diff === 0) {
				this.anchorPoint.index = 0;
				this.anchorPoint.offset = 0;
			} else {
				this.anchorPoint = Object.assign(this.anchorPoint, this.calculateOffset(this.anchorPoint, diff));
			}
			var lastAnchorPoint = this.calculateOffset(this.anchorPoint, this.el.offsetHeight);
			this.firstAttachedIndex = Math.max(this.anchorPoint.index - this.extraCount, 0);
			this.lastAttachedIndex = lastAnchorPoint.index + this.extraCount;
			this.setContent();
		},


		/**
   * 检查是否可以继续滚动
   * @return true时允许滚动，false时禁止滚动
   */
		checkScroll: function checkScroll() {
			var allow = false;
			if (!this.isbusy) {
				allow = true;
			} else if (this.el.scrollTop - this.anchorPoint.scrollTop <= 0) {
				allow = true;
			} else {
				allow = this.lastAttachedIndex < this.loadedIndex;
				if (!allow) {
					this.removeTombstone();
				}
			}
			return allow;
		},


		/**
   * 初始化墓碑尺寸
   */
		initTombstoneSize: function initTombstoneSize() {
			var tombstone = this.skeleton();
			tombstone.style.position = 'absolute';
			this.el.appendChild(tombstone);
			this.tombstoneWidth = tombstone.offsetWidth || 1;
			this.tombstoneHeight = tombstone.offsetHeight || 1;
			this.el.removeChild(tombstone);
		},


		/**
   * 初始化元素尺寸
   */
		initVnodeSize: function initVnodeSize() {
			this.vnodeList.forEach(function (item) {
				item.width = 0;
				item.height = 0;
			});
		},


		/**
   * 获取滚动高度差异
   */
		getScrollDiff: function getScrollDiff() {
			var diff = this.el.scrollTop - this.anchorPoint.scrollTop;
			this.anchorPoint.scrollTop = this.el.scrollTop;
			return diff;
		},


		/**
   * 计算偏移量
   * @param {*} param0
   * @param {*} diff
   */
		calculateOffset: function calculateOffset(_ref2, diff) {
			var index = _ref2.index,
			    offset = _ref2.offset;

			if (diff === 0) {
				return arguments[0];
			}
			diff += offset;
			var count = 0;
			if (diff > 0) {
				while (diff > 0 && index < this.vnodeList.length && this.vnodeList[index].height && this.vnodeList[index].height < diff) {
					diff -= this.vnodeList[index].height;
					index++;
				}
				if (index >= this.vnodeList.length || !this.vnodeList[index].height) {
					count = Math.floor(Math.max(diff, 0) / this.tombstoneHeight); // 向下取整
				}
			} else {
				while (diff < 0 && index > 0 && this.vnodeList[index - 1].height) {
					diff += this.vnodeList[index - 1].height;
					index--;
				}
				count = Math.max(-index, Math.ceil(Math.min(diff, 0) / this.tombstoneHeight));
			}
			index += count;
			diff -= this.tombstoneHeight * count;
			return {
				index: index,
				offset: diff
			};
		},


		/**
   * 设置内容
   */
		setContent: function setContent() {
			var _this3 = this;

			// 初始化节点
			this.vnodeList.forEach(function (vnode, index) {
				if (index < _this3.firstAttachedIndex || index > _this3.lastAttachedIndex - 1) {
					if (vnode.node) {
						_this3.el.removeChild(vnode.node);
					}
					vnode.node = null;
				}
			});

			var indexArray = [].concat(_toConsumableArray(Array(this.lastAttachedIndex - this.firstAttachedIndex).keys())).map(function (i) {
				return i + _this3.firstAttachedIndex;
			});

			// 填充默认数据
			while (this.vnodeList.length <= indexArray[indexArray.length - 1]) {
				this.vnodeList.push(this.defaultVnode());
			}

			// 插入内容
			indexArray.forEach(function (index) {
				var node = void 0;
				var passCircle = false;
				var vnode = _this3.vnodeList[index];
				if (vnode.node) {
					if (vnode.node.getAttribute('data-tombstone') && vnode.data) {
						_this3.el.removeChild(vnode.node); // 检测到元素是墓碑时删除它，后面替换成正式内容
						vnode.node = null;
					} else {
						passCircle = true;
					}
				}
				if (!passCircle) {
					if (vnode.data) {
						node = _this3.render(vnode.data);
					} else {
						node = _this3.skeleton();
						node.setAttribute('data-tombstone', 'true');
					}
					node.style.position = 'absolute';
					_this3.el.appendChild(node); // 插入内容
					vnode.offset = 0;
					vnode.node = node;
				}
			});

			// 获取节点真实宽高度
			indexArray.forEach(function (index) {
				var vnode = _this3.vnodeList[index];
				if (vnode.data && !vnode.height) {
					vnode.width = vnode.node.offsetWidth;
					vnode.height = vnode.node.offsetHeight;
				}
			});

			this.anchorPoint.scrollTop = 0;
			for (var _index = 0; _index < this.anchorPoint.index; _index++) {
				this.anchorPoint.scrollTop += this.vnodeList[_index].height || this.tombstoneHeight;
			}
			this.anchorPoint.scrollTop += this.anchorPoint.offset;

			var index = this.anchorPoint.index;
			var curPos = this.anchorPoint.scrollTop - this.anchorPoint.offset;
			while (index > this.firstAttachedIndex) {
				curPos -= this.vnodeList[index - 1].height || this.tombstoneHeight;
				index--;
			}
			while (index < this.firstAttachedIndex) {
				curPos += this.vnodeList[index].height || this.tombstoneHeight;
				index++;
			}

			indexArray.forEach(function (index) {
				var vnode = _this3.vnodeList[index];
				if (curPos !== vnode.offset) {
					vnode.node.style.transform = 'translateY(' + curPos + 'px)';
				}
				vnode.offset = curPos;
				curPos += vnode.height || _this3.tombstoneHeight;
			});

			// 手动跳转到当前位置
			this.el.scrollTop = this.anchorPoint.scrollTop;

			this.fetchData();
		},


		/**
   * 默认元素内容
   */
		defaultVnode: function defaultVnode() {
			return {
				data: null,
				node: null,
				height: 0,
				width: 0,
				offset: 0
			};
		},


		/**
   * 异步获取信息
   */
		fetchData: function fetchData() {
			var _this4 = this;

			if (this.isbusy || this.lastAttachedIndex <= this.loadedIndex) return false;
			this.isbusy = true;
			var res = this.fetch();
			(res instanceof Promise ? res : Promise.resolve(res)).then(function (datas) {
				if (datas instanceof Array) {
					datas.forEach(function (data) {
						if (_this4.vnodeList.length - 1 < _this4.loadedIndex) {
							_this4.vnodeList.push(_this4.defaultVnode());
						}
						_this4.vnodeList[_this4.loadedIndex++].data = data;
					});
					if (datas.length > 0) {
						_this4.isbusy = false;
						_this4.setContent();
					} else {
						_this4.isbusy = true;
						_this4.removeTombstone();
					}
				}
			}).catch(function () {
				_this4.removeTombstone();
			});
		},


		/**
   * 没有数据时删除多余墓碑元素
   */
		removeTombstone: function removeTombstone() {
			var sum = 0;
			for (var index = this.vnodeList.length - 1; index >= 0; index--) {
				var vnode = this.vnodeList[index];
				if (!vnode.data && vnode.node && vnode.node.getAttribute('data-tombstone')) {
					this.el.removeChild(vnode.node);
					vnode.node = null;
				} else if (++sum > this.extraCount / 2) {
					// 确保不会存在多余元素
					break;
				}
			}
		}
	};

	// 暴露接口
	window.JhVirtualScroll = JhVirtualScroll;
})();